<?php

define('APPLICATION_PATH', dirname(__FILE__));

class Http extends \Yaf\Request\Http
{
    public function __construct($request_uri, $base_uri)
    {
        parent::__construct($request_uri, $base_uri);
    }

    public function setMethod($method)
    {
        $this->method = $method;
        return $this;
    }
}

class HttpServer
{
    private $http;

    private $app;

    private $config;

    public function __construct()
    {
        $this->init();

        $this->http->on('workerStart', [$this, 'onWorkerStart']);
        $this->http->on('request', [$this, 'onRequest']);
        //异步任务 需要进一步做任务分发
        $this->http->on('task', [$this, 'onTask']);
        $this->http->on('finish', [$this, 'onFinish']);
        $this->http->on('start', [$this, 'onStart']);
        \Yaf\Registry::set('server', $this->http);
        $this->http->start();
    }

    protected function init()
    {
        $this->yafInit();
        $this->swooleInit();
    }

    protected function yafInit()
    {
        $application = new \Yaf\Application(APPLICATION_PATH . "/conf/application.ini");
        $application->bootstrap();
        $this->config = \Yaf\Registry::get('config');
        $this->app = $application;
    }

    protected function swooleInit()
    {
        $this->http = new Swoole\Http\Server('0.0.0.0', 9501);
        $path = APPLICATION_PATH . '/runtime/swoole';
        if (!is_dir($path)) {
            mkdir($path, 0777, true);
        }
        $swoole_log = APPLICATION_PATH . '/runtime/swoole/swoole.log';
        if (!file_exists($swoole_log)) {
            file_put_contents($swoole_log, '');
        }
        $swoole_pid = APPLICATION_PATH . '/runtime/swoole/swoole.pid';
        if (!file_exists($swoole_pid)) {
            file_put_contents($swoole_pid, '');
        }

        $this->http->set([
            'reactor_num' => swoole_cpu_num(),
            'worker_num' => 1, //worker进程
            'max_request' => 1000, //每个worker进程 max_request设置为1000次
            'task_worker_num' => swoole_cpu_num(), //开启task进程
            'dispatch_mode' => 2, //数据包分发策略 - 固定模式
            'log_file' => APPLICATION_PATH . '/runtime/swoole/swoole.log',
            'pid_file' => APPLICATION_PATH . '/runtime/swoole/swoole.pid',
            'task_enable_coroutine' => true,//开启task协程支持
            'enable_coroutine' => true,
            'send_yield' => true,
            'reload_async' => true,
            'task_max_request' => 0,
            'daemonize' => $this->config['swoole.daemonize'],//守护进程化
        ]);
    }

    private function hotLoad()
    {
        //方案1:定时重载
        //        \Swoole\Timer::tick(30000, function() {
        //            $this->http->reload();
        //        });
        //方案2:监控文件变动,重载 使用inotify扩展
        if (extension_loaded('inotify')) {
            $getFile = function($dir) use (&$getFile) {
                $data = [];
                if (is_dir($dir)) {
                    $files = array_diff(scandir($dir), ['.', '..']);
                    foreach ($files as $file) {
                        $data = array_merge($data, $getFile($dir . '/' . $file));
                    }
                } else {
                    $data[] = $dir;
                }
                return $data;
            };
            $fd = inotify_init();
            $list = $getFile(APPLICATION_PATH . "/application");
            foreach ($list as $item) {
                inotify_add_watch($fd, $item, IN_CREATE | IN_DELETE | IN_MODIFY);
            }
            $list = $getFile(APPLICATION_PATH . "/extend");
            foreach ($list as $item) {
                inotify_add_watch($fd, $item, IN_CREATE | IN_DELETE | IN_MODIFY);
            }
            swoole_event_add($fd, function($fd) {
                $events = inotify_read($fd);
                if (!empty($events)) {
                  $res =   $this->http->reload();
                }
            });
        } else {
            echo 'Error:Missing `inotify` extension';
        }
    }

    private function mysqlPool()
    {
        if ($this->config['mysql.rw_separate']) {
            //开启主从复制
            //添加从库的连接池
            $mysql_slave_pools = [];
            foreach ($this->config['mysql.slave'] as $slave) {
                $pool = new Swoole\Database\PDOPool((new Swoole\Database\PDOConfig)
                    ->withHost($slave['host'])
                    ->withPort($slave['port'])
                    // ->withUnixSocket('/tmp/mysql.sock')
                    ->withDbName($slave['database_name'])
                    ->withCharset($slave['charset'] ?? 'utf8')
                    ->withUsername($slave['username'])
                    ->withPassword($slave['password']),
                    $this->config['mysql.size'] ?? 64
                );
                $mysql_slave_pools[] = $pool;
            }
            \Yaf\Registry::set('mysql_slave', $mysql_slave_pools);
        }


        $pool = new Swoole\Database\PDOPool((new Swoole\Database\PDOConfig)
            ->withHost($this->config['mysql.host'])
            ->withPort($this->config['mysql.port'])
            // ->withUnixSocket('/tmp/mysql.sock')
            ->withDbName($this->config['mysql.database_name'])
            ->withCharset($this->config['mysql.charset'] ?? 'utf8')
            ->withUsername($this->config['mysql.username'])
            ->withPassword($this->config['mysql.password']),
            $this->config['mysql.size'] ?? 64
        );

        \Yaf\Registry::set('mysql', $pool);
    }

    private function redisPool()
    {
        $pool = new Swoole\Database\RedisPool((new Swoole\Database\RedisConfig)
            ->withHost($this->config['redis.host'])
            ->withPort($this->config['redis.port'])
            ->withAuth($this->config['redis.password'])
            ->withDbIndex($this->config['redis.index'] ?? 0)
            ->withTimeout($this->config['redis.timeout'] ?? 0),
            $this->config['redis.size'] ?? 64
        );
        \Yaf\Registry::set('redis', $pool);
    }

    private function rpcPool()
    {
        $pool = new Smf\ConnectionPool\ConnectionPool(
            [
                'minActive' => 10,
                'maxActive' => 30,
                'maxWaitTime' => 5,
                'maxIdleTime' => 20,
                'idleCheckInterval' => 10,
            ],
            new class implements Smf\ConnectionPool\Connectors\ConnectorInterface {
                /**
                 * Connect to the specified Server and returns the connection resource
                 * @param array $config
                 * @return mixed
                 */
                public function connect(array $config)
                {
                    $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
                    $client->set(
                        [
                            'open_length_check' => true,
                            'package_length_type' => 'NN',
                            'package_length_offset' => 0,
                            'package_body_offset' => 8,
                        ]
                    );
                    $client->connect($config['host'], $config['port'], $config['timeout']);

                    return $client;
                }

                public function disconnect($connection)
                {
                    $connection->close();
                }

                public function isConnected($connection): bool
                {
                    return $connection->isConnected();
                }


                /**
                 * Reset the connection
                 * @param mixed $connection
                 * @param array $config
                 * @return mixed
                 */
                public function reset($connection, array $config)
                {

                }

                /**
                 * Validate the connection
                 *
                 * @param mixed $connection
                 * @return bool
                 */
                public function validate($connection): bool
                {
                    return $connection instanceof Swoole\Coroutine\Client;
                }
            },
            [
                'host' => '47.114.32.24',
                'port' => '9502',
                'timeout' => 1,
            ]
        );
        $pool->init();

        \Yaf\Registry::set('rpc_client_pool', $pool);
    }

    public function onStart($server){
//        $server->task(json_encode([
//            'method' => 'Demo.demo',
//            'params' => [
//                'name' => 'name'
//            ]
//        ]));
    }

    public function onWorkerStart($request, $response)
    {
        \Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
        //mysql连接池
        $this->mysqlPool();
        //redis连接池
        $this->redisPool();
        //rpc连接池
        $this->rpcPool();

        //调试模式开启热重启
        if ($this->config['app.debug'] && $this->config['swoole.hotload']) {
//            $this->resetOpcache();
            $this->hotLoad();
        }
    }

    public function onRequest($request, $response)
    {
        if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
            $response->end();
            return;
        }

        $_GET = [];
        if (isset($request->get)) {
            $_GET = $request->get;
        }

        $_POST = [];
        if (isset($request->post)) {
            $_POST = $request->post;
        }

        $_COOKIE = [];
        if (isset($request->cookie)) {
            $_COOKIE = $request->cookie;
        }

        $_FILES = [];
        if (isset($request->files)) {
            $_FILES = $request->files;
        }

        $_SERVER = [];
        if (isset($request->server)) {
            foreach ($request->server as $key => $value) {
                $_SERVER[strtoupper($key)] = $value;
            }
        }
        if (isset($request->header)) {
            foreach ($request->header as $key => $value) {
                $_SERVER[strtoupper($key)] = $value;
            }
        }
        ob_start();
        try {
            $yaf_request = new Http($request->server['request_uri'], '/');
            $yaf_request->setMethod($request->server['request_method']);
            $this->app->getDispatcher()->dispatch($yaf_request);
        } catch (Exception $e) {
           echo $e->getMessage();
        }
        $result = ob_get_contents();
        ob_end_clean();
        $response->end($result);
    }

    public function onTask($server, $task)
    {
        $data = json_decode($task->data, true);
        if (empty($method = $data['method'])) {
            return false;
        }
        $method = explode(".", $method);
        $class = new ReflectionClass("app\\task\\$method[0]");
        $action = $method[1];
        $class->newInstance()->exec($data['params'], $action);
        return "success";
    }

    public function onFinish($serv, $task_id, $data)
    {
        echo "AsyncTask[$task_id] Finish: $data".PHP_EOL;
    }

    public function onWorkerStop($serv,$worker_id) {
        //关闭进程池
        $pools = ['mysql', 'redis'];
        foreach ($pools as $pool) {
            if (\Yaf\Registry::has($pool)) {
                \Yaf\Registry::get($pool)->close();
            }
        }
        $this->resetOpcache();
    }

    /**
     * 清楚opcache
     */
    private function resetOpcache()
    {
        if (!extension_loaded('opcache')) {
            return false;
        }
        $status = opcache_get_status();
        if ($status['opcache_enabled']) {
            opcache_reset();
        }
    }

}


new HttpServer();